package ru.ksu.ktk.diploma.sdi.mapping.resolver.impl;

import gate.util.GateException;

import java.io.IOException;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ru.edu.niimm.mapping.linguistic.NLP;
import ru.ksu.ktk.diploma.sdi.dao.ResourcesDaoImpl;
import ru.ksu.ktk.diploma.sdi.mapping.MappingElement;
import ru.ksu.ktk.diploma.sdi.mapping.Triple;
import ru.ksu.ktk.diploma.sdi.mapping.loaders.ResourceLoader;
import ru.ksu.ktk.diploma.sdi.mapping.loaders.ThesauriLoader;
import ru.ksu.ktk.diploma.sdi.mapping.resolver.MappingElementsResolver;
import ru.ksu.ktk.diploma.sdi.mapping.thesauri.Thesauri;
import ru.ksu.ktk.diploma.sdi.mapping.thesauri.ThesauriDescriptor;
import ru.ksu.ktk.diploma.sdi.util.DescriptorsUtil;
import ru.ksu.ktk.diploma.sdi.util.OntClassUtil;
import ru.ksu.ktk.diploma.sdi.util.TextUtil;

import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;

/**
 * Class that finds mapping elements for source class
 * 
 * @author coldflame
 */
public class MappingElementsResolverImpl implements MappingElementsResolver {
    protected static final Log log = LogFactory.getLog(MappingElementsResolverImpl.class);

    /**
     * thesauri model loader
     */
    private ThesauriLoader thesauriLoader;
    /**
     * Thesauri
     */
    private Thesauri thesauri;
    /**
     * links between thesauri and CIDOC CRM ontology loader
     */
    private ResourceLoader linksLoader;
    /**
     * links between thesauri and CIDOC CRM ontology
     */
    private OntModel links;
    /**
     * NLP to parse thesauri descriptors
     */
    private NLP nlpService;

    private String nlpKey;

    /*
     * (non-Javadoc)
     * 
     * @see ru.ksu.ktk.diploma.sdi.mapping.MappingElementsResolver#findMatch(com.hp.hpl.jena.ontology.OntModel)
     */
    public List<MappingElement> findMatch(OntModel sourceDataModel) {
        List<MappingElement> mapping = new ArrayList<MappingElement>();
        ExtendedIterator sourceClassesIterator = sourceDataModel.listNamedClasses();
        while (sourceClassesIterator.hasNext()){
            OntClass sourceClass = (OntClass) sourceClassesIterator.next();
            List<MappingElement> mappingElements = findMappingElements(sourceClass);
            mapping.addAll(mappingElements);
        }
        return mapping;
    }

    public Thesauri getThesauri() {
        return thesauri;
    }

    public void setThesauri(Thesauri thesauri) {
        this.thesauri = thesauri;
    }

    public ThesauriLoader getThesauriLoader() {
        return thesauriLoader;
    }

    public void setThesauriLoader(ThesauriLoader thesauriLoader) {
        this.thesauriLoader = thesauriLoader;
    }

    public ResourceLoader getLinksLoader() {
        return linksLoader;
    }

    public void setLinksLoader(ResourceLoader linksLoader) {
        this.linksLoader = linksLoader;
    }

    public NLP getNlpService() {
        return nlpService;
    }

    public void setNlpService(NLP nlpService) {
        this.nlpService = nlpService;
    }

    public String getNlpKey() {
        return nlpKey;
    }

    public void setNlpKey(String nlpKey) {
        this.nlpKey = nlpKey;
    }

    protected void init() {
        this.thesauri.setModel(thesauriLoader.load());
        this.links = linksLoader.load();
    }

    /**
     * find mapping elements for given source class
     * 
     * @param sourceClass
     * @return
     */
    private List<MappingElement> findMappingElements(OntClass sourceClass) {
        List<MappingElement> mappingElements = new ArrayList<MappingElement>();
        String sourceClassUri = sourceClass.getURI();
        String instanceText = OntClassUtil.getInstancesTextContent(sourceClass);
        if (TextUtil.isText(instanceText, thesauri.getLanguage())){
            List<ThesauriDescriptor> descriptors = getDescriptors(sourceClassUri, instanceText);
            for (ThesauriDescriptor descriptor : descriptors){
                mappingElements.addAll(getMappingElements(descriptor, sourceClassUri));
            }
        }
        return mergeRepetitives(mappingElements);
    }

    /**
     * summarize weights of repetitive mapping elements
     * 
     * @param mappingElements
     * @return
     */
    private List<MappingElement> mergeRepetitives(List<MappingElement> mappingElements) {
        List<MappingElement> mergedElements = new ArrayList<MappingElement>();
        for (MappingElement mappingElement : mappingElements){
            int elementIndex = mergedElements.indexOf(mappingElement);
            if (elementIndex != -1){
                MappingElement foundElement = mergedElements.get(elementIndex);
                foundElement.increaseWeight(mappingElement.getWeight());
            }
            else{
                mergedElements.add(mappingElement);
            }
        }
        return mergedElements;
    }

    /**
     * get mapping elements for given descriptor
     * 
     * @param descriptor thesauri descriptor
     * @param sourceClassUri source class URI
     * @return
     */

    @SuppressWarnings("unchecked")
    private List<MappingElement> getMappingElements(ThesauriDescriptor descriptor, String sourceClassUri) {
        List<MappingElement> mappingElements = new ArrayList<MappingElement>();
        OntClass descriptorBlock = getDescriptorBlock(descriptor);
        if (descriptorBlock != null){
            List<OntClass> descriptorsList = new ArrayList<OntClass>();
            descriptorsList.add(descriptorBlock);
            descriptorsList.addAll(OntClassUtil.getAllSuperClasses(descriptorBlock));

            for (OntClass curDescrBlock : descriptorsList){

                ExtendedIterator restrictionsIterator = links.listRestrictions();
                while (restrictionsIterator.hasNext()){
                    Restriction restriction = (Restriction) restrictionsIterator.next();
                    if (restriction.isSomeValuesFromRestriction()){
                        SomeValuesFromRestriction restrValue = restriction.asSomeValuesFromRestriction();
                        if (!hasUri(restrValue)){

                            OntResource union = (OntResource) restrValue.getSomeValuesFrom();
                            OntResource ni = ((OntResource) union.getPropertyValue(OWL.unionOf));
                            OntResource firstPropertyValue = (OntResource) ni.getPropertyValue(RDF.first);
                            while (firstPropertyValue != null){
                                if (haveEqualUri(firstPropertyValue, curDescrBlock)
                                    && (!isLeaf(restrValue) || hasEquivalent(restrValue))){
                                    MappingElement mappingElement =
                                        getMappingElement(sourceClassUri, restrValue, descriptor);
                                    addMappingElement(mappingElements, mappingElement, descriptor);
                                }
                                ni = ((OntResource) ni.getPropertyValue(RDF.rest));
                                firstPropertyValue = (OntResource) ni.getPropertyValue(RDF.first);
                            }
                        }
                        else
                            if (haveEqualUri(restrValue.getSomeValuesFrom(), curDescrBlock)){
                                MappingElement mappingElement =
                                    getMappingElement(sourceClassUri, restrValue, descriptor);
                                addMappingElement(mappingElements, mappingElement, descriptor);
                            }
                    }
                }

            }
        }
        return mappingElements;
    }

    private void addMappingElement(List<MappingElement> mappingElements, MappingElement mappingElement,
        ThesauriDescriptor descriptor) {
        int foundIndex = mappingElements.indexOf(mappingElement);
        if (foundIndex != -1){
            MappingElement foundElement = mappingElements.get(foundIndex);
            foundElement.addMatchedDescriptor(descriptor.getId());
        }
        else{
            mappingElement.addMatchedDescriptor(descriptor.getId());
            mappingElements.add(mappingElement);
        }
    }

    private boolean hasEquivalent(SomeValuesFromRestriction restrValue) {
        return restrValue.getEquivalentClass() != null;
    }

    private boolean isLeaf(SomeValuesFromRestriction restrValue) {
        return restrValue.getSubClass() == null;
    }

    private MappingElement getMappingElement(String sourceClassUri, SomeValuesFromRestriction restrValue,
        ThesauriDescriptor descriptor) {
        String domainUri = restrValue.getSubClass().getURI();
        OntProperty ontProperty = restrValue.getOnProperty();
        String pathUri = ontProperty.getURI();
        String rangeUri = ontProperty.getRange().getURI();
        Triple targetTriple = new Triple(domainUri, pathUri, rangeUri);
        int descriptorFrequency = descriptor.getFrequency();
        MappingElement mappingElement =
            new MappingElement.Builder(sourceClassUri, targetTriple).weight(descriptorFrequency).build();
        return mappingElement;
    }

    private OntClass getDescriptorBlock(ThesauriDescriptor descriptor) {
        Individual individual = thesauri.getBaseIndividual(descriptor.getId());
        OntClass descriptorBlock = thesauri.getOntClass(individual);
        return descriptorBlock;
    }

    private boolean haveEqualUri(Resource resource, OntClass ontClass) {
        return resource.getURI().equalsIgnoreCase(ontClass.getURI());
    }

    private boolean hasUri(SomeValuesFromRestriction restrValue) {
        return restrValue.getSomeValuesFrom().getURI() != null;
    }

    /**
     * get thesauri descriptors found in text
     * 
     * @param className ontology class name
     * @param text text source
     * @return
     */
    private List<ThesauriDescriptor> getDescriptors(String className, String text) {
        try{
            List<String> descriptorIdList =
                nlpService.execOnFieldIndex(nlpKey, thesauriLoader.getThesauriUri(), new String[] {text}, thesauri
                    .getLanguage().toString());
            return DescriptorsUtil.getThesauriDescriptors(descriptorIdList);
        }
        catch (GateException e){
            log.error(e);
            throw new IllegalStateException("couldn't parse thesauri descriptors of instance text");
        }
        catch (IOException e){
            log.error(e);
            throw new IllegalStateException("couldn't parse thesauri descriptors of instance text");
        }

    }

   }
